﻿//------------------------------------------------------------------------------------------------- 
// <copyright file="AssemblyScanner.cs" company="Christopher Hines">
// Copyright (c) Christopher Hines. All rights reserved.
// </copyright>
// <summary>
//    Scans for assemblies for auto-discovery of pluggable types
// </summary>
//-------------------------------------------------------------------------------------------------

namespace Copperfield
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.IO;
    using System.Linq;
    using System.Reflection;

    /// <summary>
    /// Scans for assemblies for auto-discovery of pluggable types
    /// </summary>
    internal static class AssemblyScanner
    {
        /// <summary>
        /// Scans assemblies for pluggable types
        /// </summary>
        internal static void ScanForPluggableTypes()
        {
            var assemblies = new List<Assembly>();

            PluggableTypeCache.ClearAll();

            var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;
            
            assemblies = assemblies.Concat(AssembliesFromPath(baseDirectory)).ToList();

            var binPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;

            if (Directory.Exists(binPath))
            {
                assemblies = assemblies.Concat(AssembliesFromPath(binPath)).ToList();
            }

            var processor = new PluggableTypeProcessor();

            foreach (var type in assemblies.SelectMany(assembly => assembly.GetTypes()))
            {
                processor.Process(type);
            }
        }

        /// <summary>
        /// Configures Plugin instances
        /// </summary>
        /// <param name="pluginConfigurationDelegate">The plugin configuration delegate</param>
        internal static void ConfigurePluginTypes(Action<Type, Type, Func<Type, Type, object>> pluginConfigurationDelegate)
        {
            var assemblies = new List<Assembly>();

            var baseDirectory = AppDomain.CurrentDomain.BaseDirectory;

            assemblies = assemblies.Concat(AssembliesFromPath(baseDirectory)).ToList();

            var binPath = AppDomain.CurrentDomain.SetupInformation.PrivateBinPath;

            if (Directory.Exists(binPath))
            {
                assemblies = assemblies.Concat(AssembliesFromPath(binPath)).ToList();
            }

            var processor = new PluginTypeProcessor(pluginConfigurationDelegate);

            foreach (var type in assemblies.SelectMany(assembly => assembly.GetTypes()))
            {
                processor.Process(type);
            }
        }

        /// <summary>
        /// Generates collection of assemblies from the specified file path
        /// </summary>
        /// <param name="path">The path from which assemblies should be collected</param>
        /// <returns>The collection of assemblies</returns>
        [SuppressMessage("Microsoft.Reliability", "CA2001:AvoidCallingProblematicMethods", Justification = "Required for dynamic type scanning")]
        private static IEnumerable<Assembly> AssembliesFromPath(string path)
        {
            var assemblies = new List<Assembly>();

            var assemblyPaths = Directory.GetFiles(path).Where(file =>
            {
                var extension = Path.GetExtension(file);

                if (extension == null)
                {
                    return false;
                }

                return
                    extension.Equals(".exe", StringComparison.OrdinalIgnoreCase) ||
                    extension.Equals(".dll", StringComparison.OrdinalIgnoreCase);
            });

            foreach (var assemblyPath in assemblyPaths)
            {
                var assembly = Assembly.LoadFrom(assemblyPath);

                if (assembly == null)
                {
                    continue;
                }

                if (!assemblies.Contains(assembly))
                {
                    assemblies.Add(assembly);
                }
            }

            return assemblies;
        }
    }
}
